/**
 *  BlueCove - Java library for Bluetooth
 *  Copyright (C) 2006-2009 Vlad Skarzhevskyy
 *
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 *
 *  @author vlads
 *  @version $Id$
 */
package net.sf.bluecove.awt;

import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Checkbox;
import java.awt.Choice;
import java.awt.Cursor;
import java.awt.Dialog;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Label;
import java.awt.Panel;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;

import net.sf.bluecove.Configuration;
import net.sf.bluecove.RemoteDeviceIheritance;
import net.sf.bluecove.TestResponderCommon;
import net.sf.bluecove.util.BluetoothTypesInfo;
import net.sf.bluecove.util.J2MEStringTokenizer;
import net.sf.bluecove.util.Storage;

import org.bluecove.tester.log.Logger;
import org.bluecove.tester.util.RuntimeDetect;

import com.intel.bluetooth.RemoteDeviceHelper;

/**
 *
 * 
 */
public class ClientConnectionDialog extends Dialog {

    private static final long serialVersionUID = 1L;

    private static final String configConnectionURL = "connectionURL";

    private static final String configLastDataSent = "lastDataSent";

    Button btnConnect, btnDisconnect, btnCancel, btnSend, btnInterrupt;

    TextField tfURL;

    Choice choiceAllURLs;

    TextField tfData;

    Choice choiceDataSendType;

    Choice choiceDataReceiveType;

    Label status;

    Checkbox cbSaveToFile;

    Timer monitorTimer;

    Object threadLocalBluetoothStack;

    String localBluetoothStackAddress = "";

    ClientConnectionThread thread;

    private Timer sendTimer;

    private int connectionWindowID = 0;

    private static Vector openConnectionDialogs = new Vector();

    private static final String RECENT_RFCOMM_URLS = "recentRFCOMM";

    private static Vector recentConnections = new Vector();

    private class ConnectionMonitor extends TimerTask {

        boolean wasConnected = false;

        boolean wasStarted = false;

        int connectingCount = 0;

        public void run() {
            if ((openConnectionDialogs.size() > 1) || (Configuration.hasManyDevices)) {
                String title = localBluetoothStackAddress + " Client Connection " + connectionWindowID;
                if (thread != null) {
                    thread.logPrefix = "[" + connectionWindowID + "]";
                    title += " " + thread.getLocalBluetoothId();
                }
                ClientConnectionDialog.this.setTitle(title);
            }
            if (thread == null) {
                if (wasConnected || wasStarted) {
                    status.setText("Idle");
                    setCursorDefault();
                    btnDisconnect.setEnabled(false);
                    btnConnect.setEnabled(true);
                    btnSend.setEnabled(false);
                    btnInterrupt.setEnabled(false);
                    wasConnected = false;
                    wasStarted = false;
                    connectingCount = 0;
                }
            } else if (thread.isRunning) {
                if (!wasConnected) {
                    setCursorDefault();
                    btnSend.setEnabled(true);
                }
                wasConnected = true;
                if (thread.receivedCount == 0) {
                    status.setText("Connected");
                } else {
                    status.setText("Received " + thread.receivedCount);
                }
            } else {
                wasStarted = true;
                if (thread.isConnecting) {
                    StringBuffer progress = new StringBuffer("Connecting ");
                    for (int i = 0; i <= connectingCount; i++) {
                        progress.append('.');
                    }
                    status.setText(progress.toString());
                    connectingCount++;
                } else {
                    setCursorDefault();
                    status.setText("Disconnected");
                    connectingCount = 0;
                }
            }
        }
    }

    public ClientConnectionDialog(Frame owner) {
        super(owner, "Client Connection", false);

        TestResponderCommon.initLocalDevice();

        Font font = new Font("Monospaced", Font.PLAIN, Configuration.screenSizeSmall ? 9 : 12);
        this.setFont(font);

        GridBagLayout gridbag = new GridBagLayout();
        GridBagConstraints c = new GridBagConstraints();
        c.fill = GridBagConstraints.BOTH;

        Panel panelItems = new BorderPanel(gridbag);
        this.add(panelItems, BorderLayout.NORTH);

        Label lURL = new Label("URL:");
        lURL.setFont(font);
        panelItems.add(lURL);
        panelItems.add(tfURL = new TextField("", 25));
        tfURL.setFont(font);
        c.gridwidth = 1;
        gridbag.setConstraints(lURL, c);
        c.gridwidth = GridBagConstraints.REMAINDER;
        gridbag.setConstraints(tfURL, c);

        if (Configuration.storage != null) {
            String url = Configuration.storage.retriveData(configConnectionURL);
            if (url == null) {
                url = Configuration.storage.retriveData(Storage.configLastServiceURL);
            }
            tfURL.setText(url);
        }

        Label lDiscovered = new Label("Discovered:");
        lDiscovered.setFont(font);
        panelItems.add(lDiscovered);
        choiceAllURLs = new Choice();
        c.gridwidth = 1;
        gridbag.setConstraints(lDiscovered, c);
        panelItems.add(choiceAllURLs);
        c.gridwidth = GridBagConstraints.REMAINDER;
        gridbag.setConstraints(choiceAllURLs, c);

        choiceAllURLs.setFont(font);

        ServiceRecords.populateChoice(choiceAllURLs, false);
        populateRecentConnections(choiceAllURLs);
        choiceAllURLs.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                selectURL();
            }
        });

        Label lData = new Label("Data:");
        lData.setFont(font);
        panelItems.add(lData);
        panelItems.add(tfData = new TextField());
        tfData.setFont(font);
        c.gridwidth = 1;
        gridbag.setConstraints(lData, c);
        c.gridwidth = GridBagConstraints.REMAINDER;
        gridbag.setConstraints(tfData, c);

        if (Configuration.storage != null) {
            String d = Configuration.storage.retriveData(configLastDataSent);
            if (d != null) {
                tfData.setText(d);
            }
        }

        Label l3 = new Label("");
        panelItems.add(l3);
        c.gridwidth = 1;
        gridbag.setConstraints(l3, c);

        panelItems.add(btnSend = new Button("Send"));
        btnSend.setFont(font);
        btnSend.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                send();
            }
        });
        btnSend.setEnabled(false);
        c.gridwidth = 1;
        gridbag.setConstraints(btnSend, c);

        choiceDataSendType = new Choice();
        choiceDataSendType.setFont(font);
        choiceDataSendType.add("as String.getBytes()+CR");
        choiceDataSendType.add("as String.getBytes()");
        choiceDataSendType.add("as parseByte(text)");
        choiceDataSendType.add("Continuously");
        // choiceDataType.add("as byte list");

        panelItems.add(choiceDataSendType);
        c.gridwidth = 1;
        gridbag.setConstraints(choiceDataSendType, c);

        Label lReceive = new Label("  Receive:");
        lReceive.setFont(font);
        panelItems.add(lReceive);
        c.gridwidth = 1;
        gridbag.setConstraints(lReceive, c);

        choiceDataReceiveType = new Choice();
        choiceDataReceiveType.setFont(font);
        choiceDataReceiveType.add("as char");
        choiceDataReceiveType.add("as charArray");
        choiceDataReceiveType.add("stats only char");
        choiceDataReceiveType.add("stats only charArray");
        choiceDataReceiveType.add("do not read");
        // choiceDataType.add("as byte list");
        // choiceDataReceiveType.add("as Echo");
        panelItems.add(choiceDataReceiveType);
        c.gridwidth = 1;
        gridbag.setConstraints(choiceDataReceiveType, c);

        choiceDataReceiveType.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                updateDataReceiveType();
            }
        });

        Label lRemainder = new Label("");
        panelItems.add(lRemainder);
        c.gridwidth = GridBagConstraints.REMAINDER;
        gridbag.setConstraints(lRemainder, c);

        Label lSaveToFile = new Label("Save to file:");
        lSaveToFile.setFont(font);
        panelItems.add(lSaveToFile);
        panelItems.add(cbSaveToFile = new Checkbox());
        c.gridwidth = 1;
        gridbag.setConstraints(lSaveToFile, c);
        c.gridwidth = GridBagConstraints.REMAINDER;
        gridbag.setConstraints(cbSaveToFile, c);
        cbSaveToFile.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                updateDataReceiveType();
            }
        });

        Label lStatus = new Label("Status:");
        lStatus.setFont(font);
        panelItems.add(lStatus);
        c.gridwidth = 1;
        gridbag.setConstraints(lStatus, c);

        status = new Label("Idle");
        status.setFont(font);
        panelItems.add(status);
        c.gridwidth = GridBagConstraints.REMAINDER;
        gridbag.setConstraints(status, c);

        Panel panelBtns = new Panel();
        this.add(panelBtns, BorderLayout.SOUTH);

        panelBtns.add(btnConnect = new Button("Connect"));
        btnConnect.setFont(font);
        btnConnect.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                connect();
            }
        });

        panelBtns.add(btnDisconnect = new Button("Disconnect"));
        btnDisconnect.setFont(font);
        btnDisconnect.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                shutdown();
            }
        });
        btnDisconnect.setEnabled(false);

        panelBtns.add(btnInterrupt = new Button("Interrupt"));
        btnInterrupt.setFont(font);
        btnInterrupt.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                interrupt();
            }
        });
        btnInterrupt.setEnabled(false);

        if (RuntimeDetect.isBlueCove) {
            Panel panelExtraBtns = panelBtns;
            if (Configuration.screenSizeSmall) {
                panelExtraBtns = new Panel();
                panelItems.add(panelExtraBtns);
                c.anchor = GridBagConstraints.EAST;
                c.gridwidth = GridBagConstraints.REMAINDER;
                ;
                gridbag.setConstraints(panelExtraBtns, c);
            }
            Button btnBond = new Button("Bond");
            btnBond.setFont(font);
            panelExtraBtns.add(btnBond);
            btnBond.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    onBond();
                }
            });

            Button btnUnBond = new Button("UnBond");
            btnUnBond.setFont(font);
            panelExtraBtns.add(btnUnBond);
            btnUnBond.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    onUnBond();
                }
            });

            Button btnInfo = new Button("Info");
            btnInfo.setFont(font);
            panelExtraBtns.add(btnInfo);
            btnInfo.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    onInfo();
                }
            });
        }

        panelBtns.add(btnCancel = new Button("Cancel"));
        btnCancel.setFont(font);
        btnCancel.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onClose();
            }
        });

        this.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                onClose();
            }
        });

        this.pack();

        synchronized (openConnectionDialogs) {
            int maxId = 0;
            for (Enumeration iter = openConnectionDialogs.elements(); iter.hasMoreElements();) {
                ClientConnectionDialog w = (ClientConnectionDialog) iter.nextElement();
                if (maxId < w.connectionWindowID) {
                    maxId = w.connectionWindowID;
                }
            }
            this.connectionWindowID = maxId + 1;
            openConnectionDialogs.add(this);
        }

        OkCancelDialog.centerParent(this);

        try {
            monitorTimer = new Timer();
            monitorTimer.schedule(new ConnectionMonitor(), 1000, 1000);
        } catch (Throwable java11) {
        }
    }

    protected void connect() {
        if (thread != null) {
            thread.shutdown();
            thread = null;
        }
        String url = tfURL.getText();
        storeRecentConnection(url);
        setCursorWait();
        thread = new ClientConnectionThread(url);
        thread.setDaemon(true);
        if (this.threadLocalBluetoothStack == null) {
            this.threadLocalBluetoothStack = Configuration.threadLocalBluetoothStack;
        }
        thread.threadLocalBluetoothStack = this.threadLocalBluetoothStack;

        if (localBluetoothStackAddress.length() == 0) {
            try {
                LocalDevice localDevice = LocalDevice.getLocalDevice();
                String bluetoothAddress = localDevice.getBluetoothAddress();
                localBluetoothStackAddress = bluetoothAddress;
                String w = TestResponderCommon.getWhiteDeviceName(bluetoothAddress);
                if (w != null) {
                    localBluetoothStackAddress += "[" + w + "]";
                }
            } catch (BluetoothStateException e) {
                Logger.debug("local error", e);
            }
        }

        thread.start();
        btnDisconnect.setEnabled(true);
        btnInterrupt.setEnabled(true);
        btnConnect.setEnabled(false);
        updateDataReceiveType();
    }

    private static void populateRecentConnections(Choice choice) {
        loadRecentConnections();
        for (Enumeration en = recentConnections.elements(); en.hasMoreElements();) {
            choice.add((String) en.nextElement());
        }
    }

    static void loadRecentConnections() {
        synchronized (recentConnections) {
            if ((recentConnections.size() > 0) || (Configuration.storage == null)) {
                return;
            }
            String urls = Configuration.storage.retriveData(RECENT_RFCOMM_URLS);
            if (urls == null) {
                return;
            }
            J2MEStringTokenizer st = new J2MEStringTokenizer(urls, "|");
            if (st.hasMoreTokens()) {
                while (st.hasMoreTokens()) {
                    String v = st.nextToken().trim();
                    if ((v.length() > 0) && (!recentConnections.contains(v))) {
                        recentConnections.add(v);
                    }
                }
            } else {
                if (urls.length() > 0) {
                    recentConnections.add(urls);
                }
            }
        }
    }

    static void storeRecentConnection(String url) {
        if (recentConnections.contains(url)) {
            recentConnections.remove(url);
        }
        recentConnections.add(url);
        Configuration.setLastServerURL(url);
        if (Configuration.storage == null) {
            return;
        }
        Configuration.storage.storeData(configConnectionURL, url);
        StringBuffer h = new StringBuffer();
        for (Enumeration iter = recentConnections.elements(); iter.hasMoreElements();) {
            h.append((String) iter.nextElement()).append("|");
        }
        Configuration.storage.storeData(RECENT_RFCOMM_URLS, h.toString());
    }

    protected void updateDataReceiveType() {
        if (thread != null) {
            thread.updateDataReceiveType(choiceDataReceiveType.getSelectedIndex(), cbSaveToFile.getState());
        }
    }

    protected void selectURL() {
        String url = ServiceRecords.getChoiceURL(choiceAllURLs);
        if (url != null) {
            tfURL.setText(url);
        }
    }

    protected void send() {
        if (thread != null) {
            String text = tfData.getText();
            if (Configuration.storage != null) {
                Configuration.storage.storeData(configLastDataSent, text);
            }
            int type = choiceDataSendType.getSelectedIndex();
            if (type == 3) {
                // Continuously
                type = 0;
                if (sendTimer == null) {
                    sendTimer = new Timer();
                    sendTimer.schedule(new TimerTask() {
                        public void run() {
                            send();
                        }
                    }, 300, 300);
                }
            } else if (sendTimer != null) {
                sendTimer.cancel();
                sendTimer = null;
            }
            byte data[];
            switch (type) {
            case 0:
                data = (text + "\n").getBytes();
                break;
            case 1:
                data = text.getBytes();
                break;
            case 2:
                J2MEStringTokenizer st = new J2MEStringTokenizer(text, ",");
                Vector bts = new Vector();
                while (st.hasMoreTokens()) {
                    bts.addElement(st.nextToken().trim());
                }
                data = new byte[bts.size()];
                int j = 0;
                for (Enumeration en = bts.elements(); en.hasMoreElements();) {
                    int i = Integer.parseInt((String) en.nextElement());
                    data[j] = (byte) (i & 0xFF);
                    j++;
                }
                break;
            default:
                data = new byte[] { 0 };
            }
            thread.send(data);
        } else if (sendTimer != null) {
            sendTimer.cancel();
            sendTimer = null;
        }
    }

    public void shutdown() {
        if (thread != null) {
            thread.shutdown();
            thread = null;
        }
        if (sendTimer != null) {
            sendTimer.cancel();
            sendTimer = null;
        }
    }

    public void interrupt() {
        if (thread != null) {
            thread.interrupt();
            Logger.debug("thread.interrupt() called");
        }
    }

    protected void onClose() {
        synchronized (openConnectionDialogs) {
            openConnectionDialogs.remove(this);
        }
        shutdown();
        try {
            monitorTimer.cancel();
        } catch (Throwable java11) {
        }
        setVisible(false);
    }

    private void setCursorWait() {
        this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
    }

    private void setCursorDefault() {
        this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    }

    private void onBond() {
        boolean test = false;
        if (test) {
            onNativeFunction();
            return;
        }

        String url = tfURL.getText();
        String pinStr = tfData.getText();
        if (pinStr.equals("null")) {
            pinStr = null;
        }
        final String pin = pinStr;
        final String deviceAddress = BluetoothTypesInfo.extractBluetoothAddress(url);
        if (deviceAddress == null) {
            Logger.error("invalid url");
            return;
        }
        final RemoteDevice device = new RemoteDeviceIheritance(deviceAddress);
        Logger.debug("authenticate:" + deviceAddress + " pin:" + pin);
        Thread t = new Thread("Authenticate") {
            public void run() {
                try {
                    setCursorWait();
                    RuntimeDetect.cldcStub.setThreadLocalBluetoothStack(threadLocalBluetoothStack);
                    boolean rc = RemoteDeviceHelper.authenticate(device, pin);
                    Logger.info("authenticate returns: " + rc);
                } catch (IOException e) {
                    Logger.error("can't authenticate", e);
                } catch (Throwable e) {
                    Logger.error("authenticate error", e);
                } finally {
                    setCursorDefault();
                }
                Logger.debug(deviceAddress + " isAuthenticated", device.isAuthenticated());
                Logger.debug(deviceAddress + " isTrustedDevice", device.isTrustedDevice());
            }
        };
        t.start();
    }

    private void onUnBond() {
        String url = tfURL.getText();
        final String deviceAddress = BluetoothTypesInfo.extractBluetoothAddress(url);
        if (deviceAddress == null) {
            Logger.error("invalid url");
            return;
        }
        final RemoteDevice device = new RemoteDeviceIheritance(deviceAddress);
        Logger.debug("removed authentication:" + deviceAddress);
        Thread t = new Thread("UnAuthenticate") {
            public void run() {
                try {
                    setCursorWait();
                    RuntimeDetect.cldcStub.setThreadLocalBluetoothStack(threadLocalBluetoothStack);
                    RemoteDeviceHelper.removeAuthentication(device);
                    Logger.info("removed authentication");
                } catch (IOException e) {
                    Logger.error("can't removed authentication", e);
                } catch (Throwable e) {
                    Logger.error("removed authentication error", e);
                } finally {
                    setCursorDefault();
                }
                Logger.debug(deviceAddress + " isAuthenticated", device.isAuthenticated());
                Logger.debug(deviceAddress + " isTrustedDevice", device.isTrustedDevice());
            }
        };
        t.start();
    }

    private void onNativeFunction() {
        String url = tfURL.getText();
        try {
            String deviceAddress = BluetoothTypesInfo.extractBluetoothAddress(url);
            if (deviceAddress == null) {
                Logger.error("invalid url");
                return;
            }
            Logger.debug(deviceAddress + " setSniffMode : " + LocalDevice.getProperty("bluecove.nativeFunction:setSniffMode:" + deviceAddress));
            // Logger.debug(deviceAddress + " cancelSniffMode : "
            // +
            // LocalDevice.getProperty("bluecove.nativeFunction:cancelSniffMode:"
            // + deviceAddress));
        } catch (Throwable e) {
            Logger.error("error", e);
        }
    }

    private void onInfo() {
        String url = tfURL.getText();
        try {
            String deviceAddress = BluetoothTypesInfo.extractBluetoothAddress(url);
            if (deviceAddress == null) {
                Logger.error("invalid url");
                return;
            }
            RemoteDevice device = new RemoteDeviceIheritance(deviceAddress);
            Logger.debug(deviceAddress + " isAuthenticated", device.isAuthenticated());
            Logger.debug(deviceAddress + " isTrustedDevice", device.isTrustedDevice());

            Logger.debug(deviceAddress + " linkMode is:" + LocalDevice.getProperty("bluecove.nativeFunction:getRemoteDeviceLinkMode:" + deviceAddress));
            Logger.debug(deviceAddress + " info:" + LocalDevice.getProperty("bluecove.nativeFunction:getRemoteDeviceVersionInfo:" + deviceAddress));

            //B4 BlueCove 2.1.1
            //			Logger.debug(deviceAddress + " RSSI:"
            //					+ LocalDevice.getProperty("bluecove.nativeFunction:getRemoteDeviceRSSI:" + deviceAddress));
            try {
                Logger.debug(deviceAddress + " RSSI:", RemoteDeviceHelper.readRSSI(device));
            } catch (IOException e) {
                Logger.debug(deviceAddress + " RSSI:", e.getMessage());
            }
        } catch (Throwable e) {
            Logger.error("error", e);
        }
    }

}
